Skip to content

fix: read GTK4 a11y trees by pinning proxy destination (#31)#32

Merged
avifenesh merged 1 commit into
mainfrom
fix/gtk4-empty-a11y-tree
Jun 21, 2026
Merged

fix: read GTK4 a11y trees by pinning proxy destination (#31)#32
avifenesh merged 1 commit into
mainfrom
fix/gtk4-empty-a11y-tree

Conversation

@avifenesh

Copy link
Copy Markdown
Collaborator

Fixes #31.

What was happening

get_app_state / list_apps returned an empty tree for GTK4 apps - a single root with role: "unknown", child_count: 0 - while GTK3 / Chromium / Electron worked.

Root cause (not what the issue guessed)

The issue suspected the atspi crate couldn't parse GTK4's app-specific object paths (/org/gnome/<App>/a11y/<uuid>). That's not it - the crate deserializes those paths fine.

The reads went through the atspi P2P trait's object_as_accessible. When an app advertises a peer-to-peer bus address, reads route over that socket. When it doesn't, the fallback builds an AccessibleProxy with a path but no destination. On the shared a11y bus that proxy can't address the app, so every call fails with ServiceUnknown - which read_node swallows into role: "unknown" / child_count: 0.

The working/broken split tracks P2P support, not the toolkit. Probed live on GNOME 50:

app toolkit advertises P2P old result
gnome-disks GTK3 yes full tree
Electron / Chrome - yes full tree
Nautilus GTK4 no empty (unknown/0)
ptyxis / mutter-x11-frames - no empty (unknown/0)

GTK4 apps just don't implement the legacy GetApplicationBusAddress, so they always hit the broken fallback.

Fix

Drop the P2P trait and open proxies with ObjectRefExt::as_accessible_proxy, which always pins the destination to the object's bus name. One open_accessible helper, all six call sites swapped.

Verification (live, GNOME 50 Wayland, real snapshot_tree)

  • Nautilus (GTK4, was broken): 1 node -> 28 nodes, descending the /org/gnome/Nautilus/a11y/<uuid> paths, application -> window "Home" -> ...
  • gnome-disks (GTK3, control): 111 nodes, unchanged - no regression

Local gates green: cargo fmt --check, cargo check --locked --all-targets, cargo clippy --locked --all-targets -- -D warnings, cargo test (120 passed).

get_app_state and list_apps returned an empty tree (one root with
role "unknown", child_count 0) for GTK4 apps like Nautilus, Text
Editor, and baobab, while GTK3/Chromium/Electron worked.

Root cause is the atspi P2P trait, not GTK4 object-path parsing.
snapshot_tree/read_node opened proxies via
AccessibilityConnection::object_as_accessible. When an app advertises
a peer-to-peer bus address it routes reads over that socket; when it
does not, the fallback builds an AccessibleProxy with a path but no
destination. On the shared a11y bus that proxy cannot address the app
and every call fails with ServiceUnknown, which read_node swallows into
role "unknown" / child_count 0.

The working/broken split tracks P2P support exactly, not the toolkit:
GTK3 gnome-disks advertises P2P and worked; GTK4 Nautilus does not
implement the legacy GetApplicationBusAddress and broke. The crate
deserializes GTK4 app-specific paths (/org/gnome/<App>/a11y/<uuid>)
correctly once the proxy has a destination.

Replace all object_as_accessible call sites with a small open_accessible
helper backed by ObjectRefExt::as_accessible_proxy, which always pins
the destination to the object's bus name. Verified live on GNOME 50:
Nautilus goes from 1 node to a full 28-node tree, gnome-disks unchanged
at 111 nodes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an issue where GTK4 applications return an empty accessibility tree by replacing the use of AccessibilityConnection::object_as_accessible with a new helper function open_accessible. This helper uses ObjectRefExt::as_accessible_proxy to ensure the destination is always pinned to the object's bus name, preventing failures on the shared accessibility bus. The reviewer provided a valuable suggestion to improve the lifetime annotations of the open_accessible function, recommending that the returned proxy's lifetime be tied to the connection rather than the temporary object reference.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/atspi_tree.rs
Comment on lines +255 to +260
async fn open_accessible<'r>(
conn: &AccessibilityConnection,
object_ref: &'r ObjectRefOwned,
) -> Result<AccessibleProxy<'r>, atspi::AtspiError> {
object_ref.as_accessible_proxy(conn.connection()).await
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The lifetime of the returned AccessibleProxy is conceptually tied to the zbus::Connection (borrowed from conn), not the ObjectRefOwned reference (object_ref).

By tying the returned proxy's lifetime to 'r (the lifetime of object_ref), you unnecessarily restrict the lifetime of the returned proxy to the scope of the temporary object_ref reference. Although this compiles in the current call sites due to covariance and localized usage, it is more idiomatic and robust to tie the returned proxy's lifetime to the connection 'conn.

Suggested change
async fn open_accessible<'r>(
conn: &AccessibilityConnection,
object_ref: &'r ObjectRefOwned,
) -> Result<AccessibleProxy<'r>, atspi::AtspiError> {
object_ref.as_accessible_proxy(conn.connection()).await
}
async fn open_accessible<'conn>(
conn: &'conn AccessibilityConnection,
object_ref: &ObjectRefOwned,
) -> Result<AccessibleProxy<'conn>, atspi::AtspiError> {
object_ref.as_accessible_proxy(conn.connection()).await
}

@avifenesh avifenesh merged commit 2ad1b6d into main Jun 21, 2026
20 checks passed
@avifenesh avifenesh deleted the fix/gtk4-empty-a11y-tree branch June 21, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GTK4 apps return empty accessibility tree (1 "unknown" node) — likely atspi crate GTK4 object-path handling

1 participant